﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ConsoleApplication18
{
    public static class Option
    {
        public static Option<T> AsOption<T>(this T arg)
        {
            return new Option<T>(arg);
        }
    }

    public struct Option<TValue>
    {
        public static readonly Option<TValue> None = new Option<TValue>();

        private readonly bool hasValue;
        private readonly TValue value;

        public Option(TValue value)
        {
            this.value = value;
            hasValue = true;
        }

        public bool HasValue
        {
            get { return hasValue; }
        }

        public TValue Value
        {
            get
            {
                if (!hasValue) throw new InvalidOperationException();
                return value;
            }
        }

        public static implicit operator Option<TValue>(TValue value)
        {
            return new Option<TValue>(value);
        }
    }

    delegate Tuple<T, Option<Chain<T>>> Chain<T>();

    static class ChainExtensions
    {
        public static Chain<T> EndChain<T>(this T arg)
        {
            return () => Tuple.Create(arg, new Option<Chain<T>>());
        }

        public static Chain<T> Concat<T>(this T arg, Func<Chain<T>> func)
        {
            return () => Tuple.Create(arg, func().AsOption());
        }

        public static Chain<TResult> Chain<T, TResult>(this T arg, Func<T, Tuple<TResult, Option<T>>> func)
        {
            return () => {
                var tuple = func(arg);
                return Tuple.Create(tuple.Item1, tuple.Item2.HasValue
                                                     ? Chain(tuple.Item2.Value, func)
                                                     : new Option<Chain<TResult>>());
            };
        }

        public static Chain<TResult> Select<TSource, TResult>(this Chain<TSource> it, Func<TSource, TResult> selector)
        {
            return it.Chain(chain => {
                var tuple = chain();
                return Tuple.Create(selector(tuple.Item1), tuple.Item2);
            });
        }

        public static IEnumerable<T> ToEnumerable<T>(this Chain<T> it)
        {
            while (true)
            {
                var tuple = it();
                yield return tuple.Item1;
                if (!tuple.Item2.HasValue) yield break;
                it = tuple.Item2.Value;
            }
        }
    }

    internal class WrapProgram
    {
        public static void Main2()
        {
            Console.WriteLine(string.Join(Environment.NewLine, Wrap("wor d word ", 5).ToEnumerable().Select(_ => _.Item1)));
        }

        static Chain<Tuple<string, string>> Wrap(string s, int columnSize)
        {
            if (columnSize <= 0) throw new ArgumentOutOfRangeException("columnSize");
            if (columnSize >= s.Length) return Tuple.Create(s, string.Empty).EndChain();
            var line = s.Substring(0, columnSize);
            if (s[columnSize] == ' ')
            {
                return SpaceCase(s, columnSize, line);
            }
            else
            {
                var lastSpacePosition = line.LastIndexOf(' ');
                return lastSpacePosition >= 0
                           ? Tuple.Create(s.Substring(0, lastSpacePosition + 1), string.Empty)
                                 .Concat(() => Wrap(s.Substring(lastSpacePosition + 1), columnSize))
                           : Tuple.Create(line, string.Empty)
                                 .Concat(() => Wrap(s.Substring(columnSize), columnSize));
            }
        }

        static Chain<Tuple<string, string>> SpaceCase(string s, int columnSize, string line)
        {
            var rightSymbols = s.Substring(columnSize);
            var trimedRightSymbols = rightSymbols.TrimStart(' ');
            return trimedRightSymbols == string.Empty
                       ? Tuple.Create(line, rightSymbols).EndChain()
                       : Tuple.Create(line, rightSymbols.Substring(0, rightSymbols.Length - trimedRightSymbols.Length))
                             .Concat(() => Wrap(trimedRightSymbols, columnSize));
        }
    }

    internal class WrapProgram2
    {
        public static IEnumerable<Tuple<string, string>> Wrap(string s, int columnSize)
        {
            if (columnSize <= 0) throw new ArgumentOutOfRangeException("columnSize");
            if (columnSize >= s.Length) return Enumerable.Repeat(Tuple.Create(s, string.Empty), 1);
            var line = s.Substring(0, columnSize);
            if (s[columnSize] == ' ')
            {
                var rightSymbols = s.Substring(columnSize);
                var trimedRightSymbols = rightSymbols.TrimStart(' ');
                return trimedRightSymbols == string.Empty
                           ? Enumerable.Repeat(Tuple.Create(line, rightSymbols), 1)
                           : Enumerable.Repeat(
                               Tuple.Create(
                                   line, rightSymbols.Substring(0, rightSymbols.Length - trimedRightSymbols.Length)),
                               1).Concat(Wrap(trimedRightSymbols, columnSize));
            }
            else
            {
                var lastSpacePosition = line.LastIndexOf(' ');
                return lastSpacePosition >= 0
                           ? Enumerable.Repeat(Tuple.Create(s.Substring(0, lastSpacePosition + 1), string.Empty), 1)
                                       .Concat(Wrap(s.Substring(lastSpacePosition + 1), columnSize))
                           : Enumerable.Repeat(Tuple.Create(line, string.Empty), 1)
                                       .Concat(Wrap(s.Substring(columnSize), columnSize));
            }
        }

        public static IEnumerable<Tuple<string, string>> Wrap2(string s, int columnSize)
        {
            if (columnSize <= 0) throw new ArgumentOutOfRangeException("columnSize");
            while (true)
            {
                if (columnSize >= s.Length)
                {
                    yield return Tuple.Create(s, string.Empty);
                    yield break;
                }
                var line = s.Substring(0, columnSize);
                string newS;
                if (s[columnSize] == ' ')
                {
                    var rightSymbols = s.Substring(columnSize);
                    var trimedRightSymbols = rightSymbols.TrimStart(' ');
                    if (trimedRightSymbols == string.Empty)
                    {
                        yield return Tuple.Create(line, rightSymbols);
                        yield break;
                    }
                    else
                    {
                        yield return Tuple.Create(
                            line, rightSymbols.Substring(0, rightSymbols.Length - trimedRightSymbols.Length));
                        newS = trimedRightSymbols;
                    }
                }
                else
                {
                    var lastSpacePosition = line.LastIndexOf(' ');
                    if (lastSpacePosition >= 0)
                    {
                        yield return Tuple.Create(s.Substring(0, lastSpacePosition + 1), string.Empty);
                        newS = s.Substring(lastSpacePosition + 1);
                    }
                    else
                    {
                        yield return Tuple.Create(line, string.Empty);
                        newS = s.Substring(columnSize);
                    }
                }
                s = newS;
            }
        }
    }
}